package com.beisita.base.exception;

import com.beisita.base.enums.ResultCodeEnum;
import com.beisita.base.result.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.util.ClassUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ControllerAdvice方式只能处理控制器抛出的异常。此时请求已经进入控制器中
 * @ControllerAdvice + @ResponseBody=@RestControllerAdvice
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler implements ErrorController {

    /**
     * 默认错误
     */
    private static final String path_default = "/error/500";

    private static final String logExceptionFormat = " Capture Exception By GlobalExceptionHandler:Code:%s Detail:%s";

    @Override
    public String getErrorPath() {
        return path_default;
    }


    //https://jira.spring.io/browse/SPR-14651
    //4.3.5 supports RedirectAttributes redirectAttributes
    @ExceptionHandler(MultipartException.class)
    public String handleError1(MultipartException e, RedirectAttributes redirectAttributes) {
        redirectAttributes.addFlashAttribute("message", e.getCause().getMessage());
        return "redirect:/uploadStatus";
    }

    /**
     * validator 统一异常封装
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        String msgs = this.handle(e.getBindingResult().getFieldErrors());
        ErrorResult error = ErrorResult.fail(ResultCodeEnum.PARAM_IS_INVALID, e, msgs);
        log.warn("URL:{} ,参数校验异常:{}", request.getRequestURI(), msgs);
        return error;
    }

    private String handle(List<FieldError> fieldErrors) {
        StringBuilder sb = new StringBuilder();
        for (FieldError obj : fieldErrors) {
            sb.append(obj.getField());
            sb.append("=[");
            sb.append(obj.getDefaultMessage());
            sb.append("]  ");
        }
        return sb.toString();
    }

    /**
     * 自定义业务异常映射,返回JSON格式提示
     */
    @ExceptionHandler(value = AppException.class)
    @ResponseBody
    public ErrorResult handlerApp(HttpServletRequest request, AppException ex) {
        ErrorResult errorResult = new ErrorResult();
        errorResult.setStatus(1);
        errorResult.setCode(ClassUtils.getShortName(ex.getClass()));
        errorResult.setMessage(ex.getMessage());
        errorResult.setUrl(String.valueOf(request.getRequestURL()));
        return errorResult;
    }

    /***
     * 404处理
     * @param ex
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResult notFountHandler(HttpServletRequest request, NoHandlerFoundException ex) {
        String method = request.getMethod();
        String path = request.getRequestURI();
        Map<String, Object> map = new HashMap<>();
        String exceptionName = ClassUtils.getShortName(ex.getClass());
        map.put("method", method);
        map.put("path", path);
        map.put("stackTrace", ex.getStackTrace());
        map.put("exceptionName", exceptionName);
        map.put("errorMessage", ex.getMessage());
        map.put("url", request.getRequestURL());
        ErrorResult result = resultFormat(ResultCodeEnum.WEB_404.getStatus(), ex);
        result.setMap(map);
        return result;
    }

    /**
     * 系统异常
     *
     * @param req
     * @param ex
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception ex) {
        Exception e = new Exception();
        //处理 UndeclaredThrowableException
        if (ex instanceof UndeclaredThrowableException) {
            e = (Exception) ((UndeclaredThrowableException) ex).getUndeclaredThrowable();
        } else {
            e = ex;
        }
        String exceptionName = ClassUtils.getShortName(ex.getClass());
        // 这里可根据不同异常引起的类做不同处理方式
        if (exceptionName.equals("NoHandlerFoundException")) {
            log.error(String.format(logExceptionFormat, ResultCodeEnum.WEB_404.getStatus(), ex.getMessage()));
            ModelAndView mav = new ModelAndView();
            mav.addObject("stackTrace", e.getStackTrace());
            mav.addObject("exceptionName", exceptionName);
            mav.addObject("errorMessage", e.getMessage());
            mav.addObject("url", req.getRequestURL());
            mav.setViewName("forward:/error/404");
            return mav;
        }
        log.error(String.format(logExceptionFormat, 1, ex.getMessage()));
        ModelAndView mav = new ModelAndView();
        mav.addObject("exceptionName", exceptionName);
        mav.addObject("stackTrace", ex.getStackTrace());
        mav.addObject("errorMessage", ex.getMessage());
        mav.addObject("url", req.getRequestURL());
        mav.setViewName("forward:/error/500");
        return mav;
    }

    private <T extends Throwable> ErrorResult resultFormat(int code, T ex) {
        ex.printStackTrace();
        log.error(String.format(logExceptionFormat, code, ex.getMessage()));
        return ErrorResult.fail(ResultCodeEnum.RUNTIME_EXCEPTION, ex);
    }

    private <T extends Throwable> ErrorResult resultFormat(ResultCodeEnum codeEnum, T ex) {
        ex.printStackTrace();
        log.error(String.format(logExceptionFormat, codeEnum.getStatus(), ex.getMessage()));
        return ErrorResult.fail(ResultCodeEnum.RUNTIME_EXCEPTION, ex);
    }

}
